library(tidyverse) # for data cleaning and plotting
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.0 ──
## ✓ ggplot2 3.3.2 ✓ purrr 0.3.4
## ✓ tibble 3.0.3 ✓ dplyr 1.0.1
## ✓ tidyr 1.1.1 ✓ stringr 1.4.0
## ✓ readr 1.3.1 ✓ forcats 0.5.0
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## x dplyr::filter() masks stats::filter()
## x dplyr::lag() masks stats::lag()
library(googlesheets4) # for reading googlesheet data
library(lubridate) # for date manipulation
##
## Attaching package: 'lubridate'
## The following objects are masked from 'package:base':
##
## date, intersect, setdiff, union
library(openintro) # for the abbr2state() function
## Loading required package: airports
## Loading required package: cherryblossom
## Loading required package: usdata
library(palmerpenguins)# for Palmer penguin data
library(maps) # for map data
##
## Attaching package: 'maps'
## The following object is masked from 'package:purrr':
##
## map
library(ggmap) # for mapping points on maps
## Google's Terms of Service: https://cloud.google.com/maps-platform/terms/.
## Please cite ggmap if you use it! See citation("ggmap") for details.
library(gplots) # for col2hex() function
##
## Attaching package: 'gplots'
## The following object is masked from 'package:stats':
##
## lowess
library(RColorBrewer) # for color palettes
library(sf) # for working with spatial data
## Linking to GEOS 3.4.2, GDAL 2.4.2, PROJ 4.8.0
library(leaflet) # for highly customizable mapping
library(ggthemes) # for more themes (including theme_map())
library(plotly) # for the ggplotly() - basic interactivity
##
## Attaching package: 'plotly'
## The following object is masked from 'package:ggmap':
##
## wind
## The following object is masked from 'package:ggplot2':
##
## last_plot
## The following object is masked from 'package:stats':
##
## filter
## The following object is masked from 'package:graphics':
##
## layout
library(gganimate) # for adding animation layers to ggplots
library(transformr) # for "tweening" (gganimate)
##
## Attaching package: 'transformr'
## The following object is masked from 'package:sf':
##
## st_normalize
library(shiny) # for creating interactive apps
library(gifski)
gs4_deauth() # To not have to authorize each time you knit.
theme_set(theme_minimal())
# SNCF Train data
small_trains <- read_csv("https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2019/2019-02-26/small_trains.csv")
## Parsed with column specification:
## cols(
## year = col_double(),
## month = col_double(),
## service = col_character(),
## departure_station = col_character(),
## arrival_station = col_character(),
## journey_time_avg = col_double(),
## total_num_trips = col_double(),
## avg_delay_all_departing = col_double(),
## avg_delay_all_arriving = col_double(),
## num_late_at_departure = col_double(),
## num_arriving_late = col_double(),
## delay_cause = col_character(),
## delayed_number = col_double()
## )
# Lisa's garden data
garden_harvest <- read_sheet("https://docs.google.com/spreadsheets/d/1DekSazCzKqPS2jnGhKue7tLxRU3GVL1oxi-4bEM5IWw/edit?usp=sharing") %>%
mutate(date = ymd(date))
## Reading from "2020_harvest"
## Range "Sheet1"
# Lisa's Mallorca cycling data
mallorca_bike_day7 <- read_csv("https://www.dropbox.com/s/zc6jan4ltmjtvy0/mallorca_bike_day7.csv?dl=1") %>%
select(1:4, speed)
## Parsed with column specification:
## cols(
## lon = col_double(),
## lat = col_double(),
## ele = col_double(),
## time = col_datetime(format = ""),
## extensions = col_double(),
## ele.num = col_double(),
## date = col_date(format = ""),
## hrminsec = col_datetime(format = ""),
## time_hr = col_double(),
## dist_km = col_double(),
## speed = col_double()
## )
# Heather Lendway's Ironman 70.3 Pan Am championships Panama data
panama_swim <- read_csv("https://raw.githubusercontent.com/llendway/gps-data/master/data/panama_swim_20160131.csv")
## Parsed with column specification:
## cols(
## lon = col_double(),
## lat = col_double(),
## time = col_datetime(format = ""),
## extensions = col_double(),
## ele = col_logical(),
## event = col_character(),
## date = col_date(format = ""),
## hrminsec = col_datetime(format = "")
## )
panama_bike <- read_csv("https://raw.githubusercontent.com/llendway/gps-data/master/data/panama_bike_20160131.csv")
## Parsed with column specification:
## cols(
## lon = col_double(),
## lat = col_double(),
## ele = col_double(),
## time = col_datetime(format = ""),
## extensions = col_double(),
## event = col_character(),
## date = col_date(format = ""),
## hrminsec = col_datetime(format = "")
## )
panama_run <- read_csv("https://raw.githubusercontent.com/llendway/gps-data/master/data/panama_run_20160131.csv")
## Parsed with column specification:
## cols(
## lon = col_double(),
## lat = col_double(),
## ele = col_double(),
## time = col_datetime(format = ""),
## extensions = col_double(),
## event = col_character(),
## date = col_date(format = ""),
## hrminsec = col_datetime(format = "")
## )
#COVID-19 data from the New York Times
covid19 <- read_csv("https://raw.githubusercontent.com/nytimes/covid-19-data/master/us-states.csv")
## Parsed with column specification:
## cols(
## date = col_date(format = ""),
## state = col_character(),
## fips = col_character(),
## cases = col_double(),
## deaths = col_double()
## )
data_site <-
"https://www.macalester.edu/~dshuman1/data/112/2014-Q4-Trips-History-Data.rds"
Trips <- readRDS(gzcon(url(data_site)))
Put your homework on GitHub!
Go here or to previous homework to remind yourself how to get set up.
Once your repository is created, you should always open your project rather than just opening an .Rmd file. You can do that by either clicking on the .Rproj file in your repository folder on your computer. Or, by going to the upper right hand corner in R Studio and clicking the arrow next to where it says Project: (None). You should see your project come up in that list if you’ve used it recently. You could also go to File –> Open Project and navigate to your .Rproj file.
Instructions
Put your name at the top of the document.
For ALL graphs, you should include appropriate labels.
Feel free to change the default theme, which I currently have set to theme_minimal().
Use good coding practice. Read the short sections on good code with pipes and ggplot2. This is part of your grade!
NEW!! With animated graphs, add eval=FALSE to the code chunk that creates the animation and saves it using anim_save(). Add another code chunk to reread the gif back into the file. See the tutorial for help.
When you are finished with ALL the exercises, uncomment the options at the top so your document looks nicer. Don’t do it before then, or else you might miss some important warnings and messages.
Warm-up exercises from tutorial
- Choose 2 graphs you have created for ANY assignment in this class and add interactivity using the
ggplotly() function.
covid_graph_1 <- covid19 %>%
filter(state %in% c("Minnesota","Wisconsin","Iowa","North Dakota","South Dakota")) %>%
group_by(state) %>%
mutate(total_cases = cumsum(cases)) %>%
ggplot(mapping = aes(x=date,y=total_cases,color=state)) +
geom_line() +
labs(title = "Covid19 Cases Over Time", x= "Date", y="Total Cases")
ggplotly(covid_graph_1)
bike_graph_1 <- Trips %>%
mutate (time_of_day = hour(sdate) + minute(sdate)/60,
week_day = wday(sdate), label = TRUE) %>%
ggplot(aes(x = time_of_day)) +
facet_wrap(vars(week_day)) +
geom_density(aes(fill = client, alpha = .5), color = NA) +
theme(axis.ticks.y = element_blank(),
axis.text.y = element_blank(),
axis.text.x = element_blank()) +
labs(title = "Trips by Casual and Registered Users over 1 Day",
x = "Time of Day",
y = "Proportion of Daily Trips",
fill = "Client Type",
alpha = NULL)
ggplotly(bike_graph_1)
## Warning: `group_by_()` is deprecated as of dplyr 0.7.0.
## Please use `group_by()` instead.
## See vignette('programming') for more help
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_warnings()` to see where this warning was generated.
- Use animation to tell an interesting story with the
small_trains dataset that contains data from the SNCF (National Society of French Railways). These are Tidy Tuesday data! Read more about it here.
I’ve used animation to look at the proportion of trains from different stations arriving late to Paris Lyon station and organizing them by mean journey time, to see whether longer journeys are more likely to arrive late. The graph is somewhat inconclusive and could require more knowledge about the local rail system to fully answer.
small_trains %>%
filter(arrival_station == "PARIS LYON") %>%
group_by(departure_station) %>%
mutate(journey_time_avg = mean(journey_time_avg),
prop_arriving_late = sum(num_arriving_late)/sum(total_num_trips)) %>%
arrange(desc(journey_time_avg)) -> paris_station_late_arr
paris_station_late_arr %>%
ggplot(aes(x = journey_time_avg,
y = prop_arriving_late)) +
geom_smooth() +
labs(title = "Late Arrivals to Paris-Lyon by Journey Length from Departure Station",
x = "Mean Journey Length from Departure Station (mins)",
y = "Proportion of Late Arrivals")+
transition_reveal(journey_time_avg)-> train_graph
animate(train_graph, nframes = 100, duration = 8, renderer = gifski_renderer())
anim_save("frenchtrains.gif")
Garden data
- In this exercise, you will create a stacked area plot that reveals itself over time (see the
geom_area() examples here). You will look at cumulative harvest of tomato varieties over time. You should do the following:
- From the
garden_harvest data, filter the data to the tomatoes and find the daily harvest in pounds for each variety.
- Then, for each variety, find the cumulative harvest in pounds.
- Use the data you just made to create a static cumulative harvest area plot, with the areas filled with different colors for each vegetable and arranged (HINT:
fct_reorder()) from most to least harvested (most on the bottom).
- Add animation to reveal the plot over date.
cum_tomato_harvest <- garden_harvest %>%
filter(vegetable == "tomatoes") %>%
complete(variety,date,fill = list(weight = 0)) %>%
group_by(variety, date) %>%
summarize(daily_weight_lbs = sum(weight*0.00220462)) %>%
mutate(cum_weight_lbs = cumsum(daily_weight_lbs)) %>%
ungroup() %>%
mutate(variety = fct_reorder(variety,cum_weight_lbs))
cum_tomato_harvest %>%
ggplot(aes(date, cum_weight_lbs, fill = variety)) +
geom_area() +
labs(title = "Cumulative Harvest of Tomatoes",
x = "Date",
Y = "Cumulative Weight (lbs)",
fill = "Variety") +
transition_reveal(date) -> tomato_graph
animate(tomato_graph, nframes = 100, duration = 8, renderer = gifski_renderer())
anim_save("tomatoes.gif")
Maps, animation, and movement!
- Map my
mallorca_bike_day7 bike ride using animation! Requirements:
- Plot on a map using
ggmap.
- Show “current” location with a red point.
- Show path up until the current point.
- Color the path according to elevation.
- Show the time in the subtitle.
- CHALLENGE: use the
ggimage package and geom_image to add a bike image instead of a red point. You can use this image. See here for an example.
- Add something of your own! And comment on if you prefer this to the static map and why or why not.
mallorca_map <- get_stamenmap(
bbox = c(right = 2.8, left = 2.3, top = 39.8, bottom = 39.5),
maptype = "terrain",
zoom = 12)
ggmap(mallorca_map) +
geom_path(data = mallorca_bike_day7,
aes(x = lon, y = lat, color = ele),
size = 3) +
labs(title = "Mallorca Bike Ride Elevation Over Time") +
transition_reveal(time) +
scale_color_viridis_c(option = "inferno") +
theme_map() +
theme(legend.background = element_blank()) -> bike_ride_gif
animate(bike_ride_gif, nframes = 100, duration = 8, renderer = gifski_renderer())
anim_save("bikeride.gif")
- In this exercise, you get to meet my sister, Heather! She is a proud Mac grad, currently works as a Data Scientist at 3M where she uses R everyday, and for a few years (while still holding a full-time job) she was a pro triathlete. You are going to map one of her races. The data from each discipline of the Ironman 70.3 Pan Am championships, Panama is in a separate file -
panama_swim, panama_bike, and panama_run. Create a similar map to the one you created with my cycling data. You will need to make some small changes: 1. combine the files (HINT: bind_rows(), 2. make the leading dot a different color depending on the event (for an extra challenge, make it a different image using `geom_image()!), 3. CHALLENGE (optional): color by speed, which you will need to compute on your own from the data. You can read Heather’s race report here. She is also in the Macalester Athletics Hall of Fame and still has records at the pool.
panama_ironman <- bind_rows(panama_swim, panama_bike, panama_run)
panama_map <- get_stamenmap(
bbox = c(left = -79.59, bottom = 8.91, right = -79.49, top = 9),
maptype = "terrain",
zoom = 13
)
ggmap(panama_map) +
geom_path(data = panama_ironman,
aes(x = lon, y = lat, color = event),
size = 2) +
transition_reveal(time) +
labs(title = "Route of Pan Am Ironman Championship",
color = "Event") +
theme_map() -> ironman_gif
animate(ironman_gif, nframes = 100, duration = 8, renderer = gifski_renderer())
anim_save("ironman.gif")
COVID-19 data
- In this exercise, you are going to replicate many of the features in this visualization by Aitish Bhatia but include all US states. Requirements:
- Create a new variable that computes the number of new cases in the past week (HINT: use the
lag() function you’ve used in a previous set of exercises). Replace missing values with 0’s using replace_na().
- Filter the data to omit rows where the cumulative case counts are less than 20.
- Create a static plot with cumulative cases on the x-axis and new cases in the past 7 days on the x-axis. Connect the points for each state over time. HINTS: use
geom_path() and add a group aesthetic. Put the x and y axis on the log scale and make the tick labels look nice - scales::comma is one option. This plot will look pretty ugly as is.
- Animate the plot to reveal the pattern by date. Display the date as the subtitle. Add a leading point to each state’s line (
geom_point()) and add the state name as a label (geom_text() - you should look at the check_overlap argument).
- Use the
animate() function to have 200 frames in your animation and make it 30 seconds long.
- Comment on what you observe.
In this graph we can observe that although New York used to have by far the most new weekly cases, Florida and California now have more, with Florida far in the lead.
covid19 %>%
group_by(state) %>%
mutate(weekly = lag(cases, n = 7, default = 0)) %>%
filter(weekly >= 20) %>%
ggplot(aes(x = cases, y = weekly, color = state)) +
scale_x_log10(label = scales::comma) +
scale_y_log10(label = scales::comma) +
geom_path() +
geom_point(size = 2) +
geom_text(aes(label = state),
check_overlap = TRUE) +
theme(legend.position = "none") +
transition_reveal(date) +
labs(title = "COVID19 Cases by State", subtitle = "Date: {frame_along}") -> covid_path
animate(covid_path, nframes = 200, duration = 30, renderer = gifski_renderer())
anim_save("covid_path.gif")
- In this exercise you will animate a map of the US, showing how cumulative COVID-19 cases per 10,000 residents has changed over time. This is similar to exercises 11 & 12 from the previous exercises, with the added animation! So, in the end, you should have something like the static map you made there, but animated over all the days. Put date in the subtitle. Comment on what you see.
In this map you can see that although Washington was the first state with a confirmed case, first New York and then Louisiana became the centers of the pandemic, with the center of the country rapidly increasing thereafter.
census_pop_est_2018 <- read_csv("https://www.dropbox.com/s/6txwv3b4ng7pepe/us_census_2018_state_pop_est.csv?dl=1") %>%
separate(state, into = c("dot","state"), extra = "merge") %>%
select(-dot) %>%
mutate(state = str_to_lower(state))
us_map <- map_data("state")
covid19 %>%
group_by(state) %>%
complete(state, date = seq.Date(min(date), max(date), by = "day")) %>%
# mutate(cases = replace_na(cases, 0)) %>%
filter(wday(date, label = TRUE) == "Fri") %>%
mutate(state = str_to_lower(state)) %>%
right_join(census_pop_est_2018,
by = "state") %>%
mutate(cases_per_10000 = cases/est_pop_2018*10000) %>%
ggplot() +
geom_map(aes(map_id = state,
fill = cases_per_10000,
group = date),
map = us_map) +
theme_map() +
labs(title = "Covid Cases per 10,000",
subtitle = "Date: {closest_state}",
fill = "Cases") +
expand_limits(x = us_map$long, y = us_map$lat) +
scale_fill_viridis_c(option = "viridis") +
theme(legend.background = element_blank()) +
transition_states(date) -> covid_map
animate(covid_map, nframes = 100, duration = 30, renderer = gifski_renderer())
anim_save("covid_map.gif")
Your first shiny app
- This app will also use the COVID data. Make sure you load that data and all the libraries you need in the
app.R file you create. Below, you will post a link to the app that you publish on shinyapps.io. You will create an app to compare states’ cumulative number of COVID cases over time. The x-axis will be number of days since 20+ cases and the y-axis will be cumulative cases on the log scale (scale_y_log10()). We use number of days since 20+ cases on the x-axis so we can make better comparisons of the curve trajectories. You will have an input box where the user can choose which states to compare (selectInput()) and have a submit button to click once the user has chosen all states they’re interested in comparing. The graph should display a different line for each state, with labels either on the graph or in a legend. Color can be used if needed.
GitHub link
- Below, provide a link to your GitHub page with this set of Weekly Exercises. Specifically, if the name of the file is 05_exercises.Rmd, provide a link to the 05_exercises.md file, which is the one that will be most readable on GitHub. If that file isn’t very readable, then provide a link to your main GitHub page.
https://github.com/ckollmer01/weekly_exercises_5
DID YOU REMEMBER TO UNCOMMENT THE OPTIONS AT THE TOP?
LS0tCnRpdGxlOiAnV2Vla2x5IEV4ZXJjaXNlcyAjNScKYXV0aG9yOiAiQ2FlZG1vbiBLb2xsbWVyLURvcnNleSIKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAga2VlcF9tZDogVFJVRQogICAgdG9jOiBUUlVFCiAgICB0b2NfZmxvYXQ6IFRSVUUKICAgIGRmX3ByaW50OiBwYWdlZAogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KI2tuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgZXJyb3I9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSkKYGBgCgpgYGB7ciBsaWJyYXJpZXN9CmxpYnJhcnkodGlkeXZlcnNlKSAgICAgIyBmb3IgZGF0YSBjbGVhbmluZyBhbmQgcGxvdHRpbmcKbGlicmFyeShnb29nbGVzaGVldHM0KSAjIGZvciByZWFkaW5nIGdvb2dsZXNoZWV0IGRhdGEKbGlicmFyeShsdWJyaWRhdGUpICAgICAjIGZvciBkYXRlIG1hbmlwdWxhdGlvbgpsaWJyYXJ5KG9wZW5pbnRybykgICAgICMgZm9yIHRoZSBhYmJyMnN0YXRlKCkgZnVuY3Rpb24KbGlicmFyeShwYWxtZXJwZW5ndWlucykjIGZvciBQYWxtZXIgcGVuZ3VpbiBkYXRhCmxpYnJhcnkobWFwcykgICAgICAgICAgIyBmb3IgbWFwIGRhdGEKbGlicmFyeShnZ21hcCkgICAgICAgICAjIGZvciBtYXBwaW5nIHBvaW50cyBvbiBtYXBzCmxpYnJhcnkoZ3Bsb3RzKSAgICAgICAgIyBmb3IgY29sMmhleCgpIGZ1bmN0aW9uCmxpYnJhcnkoUkNvbG9yQnJld2VyKSAgIyBmb3IgY29sb3IgcGFsZXR0ZXMKbGlicmFyeShzZikgICAgICAgICAgICAjIGZvciB3b3JraW5nIHdpdGggc3BhdGlhbCBkYXRhCmxpYnJhcnkobGVhZmxldCkgICAgICAgIyBmb3IgaGlnaGx5IGN1c3RvbWl6YWJsZSBtYXBwaW5nCmxpYnJhcnkoZ2d0aGVtZXMpICAgICAgIyBmb3IgbW9yZSB0aGVtZXMgKGluY2x1ZGluZyB0aGVtZV9tYXAoKSkKbGlicmFyeShwbG90bHkpICAgICAgICAjIGZvciB0aGUgZ2dwbG90bHkoKSAtIGJhc2ljIGludGVyYWN0aXZpdHkKbGlicmFyeShnZ2FuaW1hdGUpICAgICAjIGZvciBhZGRpbmcgYW5pbWF0aW9uIGxheWVycyB0byBnZ3Bsb3RzCmxpYnJhcnkodHJhbnNmb3JtcikgICAgIyBmb3IgInR3ZWVuaW5nIiAoZ2dhbmltYXRlKQpsaWJyYXJ5KHNoaW55KSAgICAgICAgICMgZm9yIGNyZWF0aW5nIGludGVyYWN0aXZlIGFwcHMKbGlicmFyeShnaWZza2kpCmdzNF9kZWF1dGgoKSAgICAgICAgICAgIyBUbyBub3QgaGF2ZSB0byBhdXRob3JpemUgZWFjaCB0aW1lIHlvdSBrbml0Lgp0aGVtZV9zZXQodGhlbWVfbWluaW1hbCgpKQpgYGAKCmBgYHtyIGRhdGF9CiMgU05DRiBUcmFpbiBkYXRhCnNtYWxsX3RyYWlucyA8LSByZWFkX2NzdigiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3Jmb3JkYXRhc2NpZW5jZS90aWR5dHVlc2RheS9tYXN0ZXIvZGF0YS8yMDE5LzIwMTktMDItMjYvc21hbGxfdHJhaW5zLmNzdiIpIAoKIyBMaXNhJ3MgZ2FyZGVuIGRhdGEKZ2FyZGVuX2hhcnZlc3QgPC0gcmVhZF9zaGVldCgiaHR0cHM6Ly9kb2NzLmdvb2dsZS5jb20vc3ByZWFkc2hlZXRzL2QvMURla1NhekN6S3FQUzJqbkdoS3VlN3RMeFJVM0dWTDFveGktNGJFTTVJV3cvZWRpdD91c3A9c2hhcmluZyIpICU+JSAKICBtdXRhdGUoZGF0ZSA9IHltZChkYXRlKSkKCiMgTGlzYSdzIE1hbGxvcmNhIGN5Y2xpbmcgZGF0YQptYWxsb3JjYV9iaWtlX2RheTcgPC0gcmVhZF9jc3YoImh0dHBzOi8vd3d3LmRyb3Bib3guY29tL3MvemM2amFuNGx0bWp0dnkwL21hbGxvcmNhX2Jpa2VfZGF5Ny5jc3Y/ZGw9MSIpICU+JSAKICBzZWxlY3QoMTo0LCBzcGVlZCkKCiMgSGVhdGhlciBMZW5kd2F5J3MgSXJvbm1hbiA3MC4zIFBhbiBBbSBjaGFtcGlvbnNoaXBzIFBhbmFtYSBkYXRhCnBhbmFtYV9zd2ltIDwtIHJlYWRfY3N2KCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vbGxlbmR3YXkvZ3BzLWRhdGEvbWFzdGVyL2RhdGEvcGFuYW1hX3N3aW1fMjAxNjAxMzEuY3N2IikKCnBhbmFtYV9iaWtlIDwtIHJlYWRfY3N2KCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vbGxlbmR3YXkvZ3BzLWRhdGEvbWFzdGVyL2RhdGEvcGFuYW1hX2Jpa2VfMjAxNjAxMzEuY3N2IikKCnBhbmFtYV9ydW4gPC0gcmVhZF9jc3YoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9sbGVuZHdheS9ncHMtZGF0YS9tYXN0ZXIvZGF0YS9wYW5hbWFfcnVuXzIwMTYwMTMxLmNzdiIpCgojQ09WSUQtMTkgZGF0YSBmcm9tIHRoZSBOZXcgWW9yayBUaW1lcwpjb3ZpZDE5IDwtIHJlYWRfY3N2KCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vbnl0aW1lcy9jb3ZpZC0xOS1kYXRhL21hc3Rlci91cy1zdGF0ZXMuY3N2IikKCmRhdGFfc2l0ZSA8LSAKICAiaHR0cHM6Ly93d3cubWFjYWxlc3Rlci5lZHUvfmRzaHVtYW4xL2RhdGEvMTEyLzIwMTQtUTQtVHJpcHMtSGlzdG9yeS1EYXRhLnJkcyIgClRyaXBzIDwtIHJlYWRSRFMoZ3pjb24odXJsKGRhdGFfc2l0ZSkpKQoKYGBgCgojIyBQdXQgeW91ciBob21ld29yayBvbiBHaXRIdWIhCgpHbyBbaGVyZV0oaHR0cHM6Ly9naXRodWIuY29tL2xsZW5kd2F5L2dpdGh1Yl9mb3JfY29sbGFib3JhdGlvbi9ibG9iL21hc3Rlci9naXRodWJfZm9yX2NvbGxhYm9yYXRpb24ubWQpIG9yIHRvIHByZXZpb3VzIGhvbWV3b3JrIHRvIHJlbWluZCB5b3Vyc2VsZiBob3cgdG8gZ2V0IHNldCB1cC4gCgpPbmNlIHlvdXIgcmVwb3NpdG9yeSBpcyBjcmVhdGVkLCB5b3Ugc2hvdWxkIGFsd2F5cyBvcGVuIHlvdXIgKipwcm9qZWN0KiogcmF0aGVyIHRoYW4ganVzdCBvcGVuaW5nIGFuIC5SbWQgZmlsZS4gWW91IGNhbiBkbyB0aGF0IGJ5IGVpdGhlciBjbGlja2luZyBvbiB0aGUgLlJwcm9qIGZpbGUgaW4geW91ciByZXBvc2l0b3J5IGZvbGRlciBvbiB5b3VyIGNvbXB1dGVyLiBPciwgYnkgZ29pbmcgdG8gdGhlIHVwcGVyIHJpZ2h0IGhhbmQgY29ybmVyIGluIFIgU3R1ZGlvIGFuZCBjbGlja2luZyB0aGUgYXJyb3cgbmV4dCB0byB3aGVyZSBpdCBzYXlzIFByb2plY3Q6IChOb25lKS4gWW91IHNob3VsZCBzZWUgeW91ciBwcm9qZWN0IGNvbWUgdXAgaW4gdGhhdCBsaXN0IGlmIHlvdSd2ZSB1c2VkIGl0IHJlY2VudGx5LiBZb3UgY291bGQgYWxzbyBnbyB0byBGaWxlIC0tPiBPcGVuIFByb2plY3QgYW5kIG5hdmlnYXRlIHRvIHlvdXIgLlJwcm9qIGZpbGUuIAoKIyMgSW5zdHJ1Y3Rpb25zCgoqIFB1dCB5b3VyIG5hbWUgYXQgdGhlIHRvcCBvZiB0aGUgZG9jdW1lbnQuIAoKKiAqKkZvciBBTEwgZ3JhcGhzLCB5b3Ugc2hvdWxkIGluY2x1ZGUgYXBwcm9wcmlhdGUgbGFiZWxzLioqIAoKKiBGZWVsIGZyZWUgdG8gY2hhbmdlIHRoZSBkZWZhdWx0IHRoZW1lLCB3aGljaCBJIGN1cnJlbnRseSBoYXZlIHNldCB0byBgdGhlbWVfbWluaW1hbCgpYC4gCgoqIFVzZSBnb29kIGNvZGluZyBwcmFjdGljZS4gUmVhZCB0aGUgc2hvcnQgc2VjdGlvbnMgb24gZ29vZCBjb2RlIHdpdGggW3BpcGVzXShodHRwczovL3N0eWxlLnRpZHl2ZXJzZS5vcmcvcGlwZXMuaHRtbCkgYW5kIFtnZ3Bsb3QyXShodHRwczovL3N0eWxlLnRpZHl2ZXJzZS5vcmcvZ2dwbG90Mi5odG1sKS4gKipUaGlzIGlzIHBhcnQgb2YgeW91ciBncmFkZSEqKgoKKiAqKk5FVyEhKiogV2l0aCBhbmltYXRlZCBncmFwaHMsIGFkZCBgZXZhbD1GQUxTRWAgdG8gdGhlIGNvZGUgY2h1bmsgdGhhdCBjcmVhdGVzIHRoZSBhbmltYXRpb24gYW5kIHNhdmVzIGl0IHVzaW5nIGBhbmltX3NhdmUoKWAuIEFkZCBhbm90aGVyIGNvZGUgY2h1bmsgdG8gcmVyZWFkIHRoZSBnaWYgYmFjayBpbnRvIHRoZSBmaWxlLiBTZWUgdGhlIFt0dXRvcmlhbF0oaHR0cHM6Ly9hbmltYXRpb24tYW5kLWludGVyYWN0aXZpdHktaW4tci5uZXRsaWZ5LmFwcC8pIGZvciBoZWxwLiAKCiogV2hlbiB5b3UgYXJlIGZpbmlzaGVkIHdpdGggQUxMIHRoZSBleGVyY2lzZXMsIHVuY29tbWVudCB0aGUgb3B0aW9ucyBhdCB0aGUgdG9wIHNvIHlvdXIgZG9jdW1lbnQgbG9va3MgbmljZXIuIERvbid0IGRvIGl0IGJlZm9yZSB0aGVuLCBvciBlbHNlIHlvdSBtaWdodCBtaXNzIHNvbWUgaW1wb3J0YW50IHdhcm5pbmdzIGFuZCBtZXNzYWdlcy4KCiMjIFdhcm0tdXAgZXhlcmNpc2VzIGZyb20gdHV0b3JpYWwKCiAgMS4gQ2hvb3NlIDIgZ3JhcGhzIHlvdSBoYXZlIGNyZWF0ZWQgZm9yIEFOWSBhc3NpZ25tZW50IGluIHRoaXMgY2xhc3MgYW5kIGFkZCBpbnRlcmFjdGl2aXR5IHVzaW5nIHRoZSBgZ2dwbG90bHkoKWAgZnVuY3Rpb24uIAogIApgYGB7cn0KY292aWRfZ3JhcGhfMSA8LSBjb3ZpZDE5ICU+JQogIGZpbHRlcihzdGF0ZSAlaW4lIGMoIk1pbm5lc290YSIsIldpc2NvbnNpbiIsIklvd2EiLCJOb3J0aCBEYWtvdGEiLCJTb3V0aCBEYWtvdGEiKSkgJT4lCiAgZ3JvdXBfYnkoc3RhdGUpICU+JQogIG11dGF0ZSh0b3RhbF9jYXNlcyA9IGN1bXN1bShjYXNlcykpICU+JQogIGdncGxvdChtYXBwaW5nID0gYWVzKHg9ZGF0ZSx5PXRvdGFsX2Nhc2VzLGNvbG9yPXN0YXRlKSkgKwogIGdlb21fbGluZSgpICsKICBsYWJzKHRpdGxlID0gIkNvdmlkMTkgQ2FzZXMgT3ZlciBUaW1lIiwgeD0gIkRhdGUiLCB5PSJUb3RhbCBDYXNlcyIpCgpnZ3Bsb3RseShjb3ZpZF9ncmFwaF8xKQpgYGAKICAKYGBge3J9CmJpa2VfZ3JhcGhfMSA8LSBUcmlwcyAlPiUKICBtdXRhdGUgKHRpbWVfb2ZfZGF5ID0gaG91cihzZGF0ZSkgKyBtaW51dGUoc2RhdGUpLzYwLAogICAgICAgICAgd2Vla19kYXkgPSB3ZGF5KHNkYXRlKSwgbGFiZWwgPSBUUlVFKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB0aW1lX29mX2RheSkpICsKICBmYWNldF93cmFwKHZhcnMod2Vla19kYXkpKSArCiAgZ2VvbV9kZW5zaXR5KGFlcyhmaWxsID0gY2xpZW50LCBhbHBoYSA9IC41KSwgY29sb3IgPSBOQSkgKwogIHRoZW1lKGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIGxhYnModGl0bGUgPSAiVHJpcHMgYnkgQ2FzdWFsIGFuZCBSZWdpc3RlcmVkIFVzZXJzIG92ZXIgMSBEYXkiLAogICAgICAgeCA9ICJUaW1lIG9mIERheSIsCiAgICAgICB5ID0gIlByb3BvcnRpb24gb2YgRGFpbHkgVHJpcHMiLAogICAgICAgZmlsbCA9ICJDbGllbnQgVHlwZSIsCiAgICAgICBhbHBoYSA9IE5VTEwpCgpnZ3Bsb3RseShiaWtlX2dyYXBoXzEpCmBgYAogIAogIAogIDIuIFVzZSBhbmltYXRpb24gdG8gdGVsbCBhbiBpbnRlcmVzdGluZyBzdG9yeSB3aXRoIHRoZSBgc21hbGxfdHJhaW5zYCBkYXRhc2V0IHRoYXQgY29udGFpbnMgZGF0YSBmcm9tIHRoZSBTTkNGIChOYXRpb25hbCBTb2NpZXR5IG9mIEZyZW5jaCBSYWlsd2F5cykuIFRoZXNlIGFyZSBUaWR5IFR1ZXNkYXkgZGF0YSEgUmVhZCBtb3JlIGFib3V0IGl0IFtoZXJlXShodHRwczovL2dpdGh1Yi5jb20vcmZvcmRhdGFzY2llbmNlL3RpZHl0dWVzZGF5L3RyZWUvbWFzdGVyL2RhdGEvMjAxOS8yMDE5LTAyLTI2KS4KCkkndmUgdXNlZCBhbmltYXRpb24gdG8gbG9vayBhdCB0aGUgcHJvcG9ydGlvbiBvZiB0cmFpbnMgZnJvbSBkaWZmZXJlbnQgc3RhdGlvbnMgYXJyaXZpbmcgbGF0ZSB0byBQYXJpcyBMeW9uIHN0YXRpb24gYW5kIG9yZ2FuaXppbmcgdGhlbSBieSBtZWFuIGpvdXJuZXkgdGltZSwgdG8gc2VlIHdoZXRoZXIgbG9uZ2VyIGpvdXJuZXlzIGFyZSBtb3JlIGxpa2VseSB0byBhcnJpdmUgbGF0ZS4gVGhlIGdyYXBoIGlzIHNvbWV3aGF0IGluY29uY2x1c2l2ZSBhbmQgY291bGQgcmVxdWlyZSBtb3JlIGtub3dsZWRnZSBhYm91dCB0aGUgbG9jYWwgcmFpbCBzeXN0ZW0gdG8gZnVsbHkgYW5zd2VyLgoKYGBge3IsIGV2YWwgPSBGQUxTRX0Kc21hbGxfdHJhaW5zICU+JQogIGZpbHRlcihhcnJpdmFsX3N0YXRpb24gPT0gIlBBUklTIExZT04iKSAlPiUKICBncm91cF9ieShkZXBhcnR1cmVfc3RhdGlvbikgJT4lCiAgbXV0YXRlKGpvdXJuZXlfdGltZV9hdmcgPSBtZWFuKGpvdXJuZXlfdGltZV9hdmcpLAogICAgICAgICBwcm9wX2Fycml2aW5nX2xhdGUgPSBzdW0obnVtX2Fycml2aW5nX2xhdGUpL3N1bSh0b3RhbF9udW1fdHJpcHMpKSAlPiUKICBhcnJhbmdlKGRlc2Moam91cm5leV90aW1lX2F2ZykpIC0+IHBhcmlzX3N0YXRpb25fbGF0ZV9hcnIKCnBhcmlzX3N0YXRpb25fbGF0ZV9hcnIgJT4lICAKZ2dwbG90KGFlcyh4ID0gam91cm5leV90aW1lX2F2ZywgCiAgICAgICAgICAgICB5ID0gcHJvcF9hcnJpdmluZ19sYXRlKSkgKwogIGdlb21fc21vb3RoKCkgKyAKICBsYWJzKHRpdGxlID0gIkxhdGUgQXJyaXZhbHMgdG8gUGFyaXMtTHlvbiBieSBKb3VybmV5IExlbmd0aCBmcm9tIERlcGFydHVyZSBTdGF0aW9uIiwKICAgICAgIHggPSAiTWVhbiBKb3VybmV5IExlbmd0aCBmcm9tIERlcGFydHVyZSBTdGF0aW9uIChtaW5zKSIsCiAgICAgICB5ID0gIlByb3BvcnRpb24gb2YgTGF0ZSBBcnJpdmFscyIpKwogIHRyYW5zaXRpb25fcmV2ZWFsKGpvdXJuZXlfdGltZV9hdmcpLT4gdHJhaW5fZ3JhcGggCgphbmltYXRlKHRyYWluX2dyYXBoLCBuZnJhbWVzID0gMTAwLCBkdXJhdGlvbiA9IDgsIHJlbmRlcmVyID0gZ2lmc2tpX3JlbmRlcmVyKCkpCgphbmltX3NhdmUoImZyZW5jaHRyYWlucy5naWYiKQpgYGAKCgojIyBHYXJkZW4gZGF0YQoKICAzLiBJbiB0aGlzIGV4ZXJjaXNlLCB5b3Ugd2lsbCBjcmVhdGUgYSBzdGFja2VkIGFyZWEgcGxvdCB0aGF0IHJldmVhbHMgaXRzZWxmIG92ZXIgdGltZSAoc2VlIHRoZSBgZ2VvbV9hcmVhKClgIGV4YW1wbGVzIFtoZXJlXShodHRwczovL2dncGxvdDIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvcG9zaXRpb25fc3RhY2suaHRtbCkpLiBZb3Ugd2lsbCBsb29rIGF0IGN1bXVsYXRpdmUgaGFydmVzdCBvZiB0b21hdG8gdmFyaWV0aWVzIG92ZXIgdGltZS4gWW91IHNob3VsZCBkbyB0aGUgZm9sbG93aW5nOgogICogRnJvbSB0aGUgYGdhcmRlbl9oYXJ2ZXN0YCBkYXRhLCBmaWx0ZXIgdGhlIGRhdGEgdG8gdGhlIHRvbWF0b2VzIGFuZCBmaW5kIHRoZSAqZGFpbHkqIGhhcnZlc3QgaW4gcG91bmRzIGZvciBlYWNoIHZhcmlldHkuICAKICAqIFRoZW4sIGZvciBlYWNoIHZhcmlldHksIGZpbmQgdGhlIGN1bXVsYXRpdmUgaGFydmVzdCBpbiBwb3VuZHMuICAKICAqIFVzZSB0aGUgZGF0YSB5b3UganVzdCBtYWRlIHRvIGNyZWF0ZSBhIHN0YXRpYyBjdW11bGF0aXZlIGhhcnZlc3QgYXJlYSBwbG90LCB3aXRoIHRoZSBhcmVhcyBmaWxsZWQgd2l0aCBkaWZmZXJlbnQgY29sb3JzIGZvciBlYWNoIHZlZ2V0YWJsZSBhbmQgYXJyYW5nZWQgKEhJTlQ6IGBmY3RfcmVvcmRlcigpYCkgZnJvbSBtb3N0IHRvIGxlYXN0IGhhcnZlc3RlZCAobW9zdCBvbiB0aGUgYm90dG9tKS4gIAogICogQWRkIGFuaW1hdGlvbiB0byByZXZlYWwgdGhlIHBsb3Qgb3ZlciBkYXRlLiAKCmBgYHtyLGV2YWw9RkFMU0V9CmN1bV90b21hdG9faGFydmVzdCA8LSBnYXJkZW5faGFydmVzdCAlPiUKICBmaWx0ZXIodmVnZXRhYmxlID09ICJ0b21hdG9lcyIpICU+JQogIGNvbXBsZXRlKHZhcmlldHksZGF0ZSxmaWxsID0gbGlzdCh3ZWlnaHQgPSAwKSkgJT4lCiAgZ3JvdXBfYnkodmFyaWV0eSwgZGF0ZSkgJT4lCiAgc3VtbWFyaXplKGRhaWx5X3dlaWdodF9sYnMgPSBzdW0od2VpZ2h0KjAuMDAyMjA0NjIpKSAlPiUKICBtdXRhdGUoY3VtX3dlaWdodF9sYnMgPSBjdW1zdW0oZGFpbHlfd2VpZ2h0X2xicykpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBtdXRhdGUodmFyaWV0eSA9IGZjdF9yZW9yZGVyKHZhcmlldHksY3VtX3dlaWdodF9sYnMpKQoKY3VtX3RvbWF0b19oYXJ2ZXN0ICU+JQogIGdncGxvdChhZXMoZGF0ZSwgY3VtX3dlaWdodF9sYnMsIGZpbGwgPSB2YXJpZXR5KSkgKwogIGdlb21fYXJlYSgpICsKICBsYWJzKHRpdGxlID0gIkN1bXVsYXRpdmUgSGFydmVzdCBvZiBUb21hdG9lcyIsCiAgICAgICB4ID0gIkRhdGUiLAogICAgICAgWSA9ICJDdW11bGF0aXZlIFdlaWdodCAobGJzKSIsCiAgICAgICBmaWxsID0gIlZhcmlldHkiKSArCiAgdHJhbnNpdGlvbl9yZXZlYWwoZGF0ZSkgLT4gdG9tYXRvX2dyYXBoCgphbmltYXRlKHRvbWF0b19ncmFwaCwgbmZyYW1lcyA9IDEwMCwgZHVyYXRpb24gPSA4LCByZW5kZXJlciA9IGdpZnNraV9yZW5kZXJlcigpKQoKYW5pbV9zYXZlKCJ0b21hdG9lcy5naWYiKQpgYGAKCgojIyBNYXBzLCBhbmltYXRpb24sIGFuZCBtb3ZlbWVudCEKCiAgNC4gTWFwIG15IGBtYWxsb3JjYV9iaWtlX2RheTdgIGJpa2UgcmlkZSB1c2luZyBhbmltYXRpb24hIAogIFJlcXVpcmVtZW50czoKICAqIFBsb3Qgb24gYSBtYXAgdXNpbmcgYGdnbWFwYC4gIAogICogU2hvdyAiY3VycmVudCIgbG9jYXRpb24gd2l0aCBhIHJlZCBwb2ludC4gCiAgKiBTaG93IHBhdGggdXAgdW50aWwgdGhlIGN1cnJlbnQgcG9pbnQuICAKICAqIENvbG9yIHRoZSBwYXRoIGFjY29yZGluZyB0byBlbGV2YXRpb24uICAKICAqIFNob3cgdGhlIHRpbWUgaW4gdGhlIHN1YnRpdGxlLiAgCiAgKiBDSEFMTEVOR0U6IHVzZSB0aGUgYGdnaW1hZ2VgIHBhY2thZ2UgYW5kIGBnZW9tX2ltYWdlYCB0byBhZGQgYSBiaWtlIGltYWdlIGluc3RlYWQgb2YgYSByZWQgcG9pbnQuIFlvdSBjYW4gdXNlIFt0aGlzXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vbGxlbmR3YXkvYW5pbWF0aW9uX2FuZF9pbnRlcmFjdGl2aXR5L21hc3Rlci9iaWtlLnBuZykgaW1hZ2UuIFNlZSBbaGVyZV0oaHR0cHM6Ly9nb29kZWthdC5naXRodWIuaW8vcHJlc2VudGF0aW9ucy8yMDE5LWlzdWdnLWdnYW5pbWF0ZS1zcG9va3kvc2xpZGVzLmh0bWwjMzUpIGZvciBhbiBleGFtcGxlLiAKICAqIEFkZCBzb21ldGhpbmcgb2YgeW91ciBvd24hIEFuZCBjb21tZW50IG9uIGlmIHlvdSBwcmVmZXIgdGhpcyB0byB0aGUgc3RhdGljIG1hcCBhbmQgd2h5IG9yIHdoeSBub3QuCiAgCiAgCmBgYHtyLGV2YWw9RkFMU0V9Cm1hbGxvcmNhX21hcCA8LSBnZXRfc3RhbWVubWFwKAogICAgICAgICAgICAgICAgYmJveCA9IGMocmlnaHQgPSAyLjgsIGxlZnQgPSAyLjMsIHRvcCA9IDM5LjgsIGJvdHRvbSA9IDM5LjUpLAogICAgICAgICAgICAgICAgbWFwdHlwZSA9ICJ0ZXJyYWluIiwKICAgICAgICAgICAgICAgIHpvb20gPSAxMikKCmdnbWFwKG1hbGxvcmNhX21hcCkgKwogIGdlb21fcGF0aChkYXRhID0gbWFsbG9yY2FfYmlrZV9kYXk3LAogICAgICAgICAgICAgYWVzKHggPSBsb24sIHkgPSBsYXQsIGNvbG9yID0gZWxlKSwKICAgICAgICAgICAgIHNpemUgPSAzKSArCiAgbGFicyh0aXRsZSA9ICJNYWxsb3JjYSBCaWtlIFJpZGUgRWxldmF0aW9uIE92ZXIgVGltZSIpICsKICB0cmFuc2l0aW9uX3JldmVhbCh0aW1lKSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19jKG9wdGlvbiA9ICJpbmZlcm5vIikgKwogIHRoZW1lX21hcCgpICsKICB0aGVtZShsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkgLT4gYmlrZV9yaWRlX2dpZgoKYW5pbWF0ZShiaWtlX3JpZGVfZ2lmLCBuZnJhbWVzID0gMTAwLCBkdXJhdGlvbiA9IDgsIHJlbmRlcmVyID0gZ2lmc2tpX3JlbmRlcmVyKCkpCgphbmltX3NhdmUoImJpa2VyaWRlLmdpZiIpCmBgYAogIAogIAogIDUuIEluIHRoaXMgZXhlcmNpc2UsIHlvdSBnZXQgdG8gbWVldCBteSBzaXN0ZXIsIEhlYXRoZXIhIFNoZSBpcyBhIHByb3VkIE1hYyBncmFkLCBjdXJyZW50bHkgd29ya3MgYXMgYSBEYXRhIFNjaWVudGlzdCBhdCAzTSB3aGVyZSBzaGUgdXNlcyBSIGV2ZXJ5ZGF5LCBhbmQgZm9yIGEgZmV3IHllYXJzICh3aGlsZSBzdGlsbCBob2xkaW5nIGEgZnVsbC10aW1lIGpvYikgc2hlIHdhcyBhIHBybyB0cmlhdGhsZXRlLiBZb3UgYXJlIGdvaW5nIHRvIG1hcCBvbmUgb2YgaGVyIHJhY2VzLiBUaGUgZGF0YSBmcm9tIGVhY2ggZGlzY2lwbGluZSBvZiB0aGUgSXJvbm1hbiA3MC4zIFBhbiBBbSBjaGFtcGlvbnNoaXBzLCBQYW5hbWEgaXMgaW4gYSBzZXBhcmF0ZSBmaWxlIC0gYHBhbmFtYV9zd2ltYCwgYHBhbmFtYV9iaWtlYCwgYW5kIGBwYW5hbWFfcnVuYC4gQ3JlYXRlIGEgc2ltaWxhciBtYXAgdG8gdGhlIG9uZSB5b3UgY3JlYXRlZCB3aXRoIG15IGN5Y2xpbmcgZGF0YS4gWW91IHdpbGwgbmVlZCB0byBtYWtlIHNvbWUgc21hbGwgY2hhbmdlczogMS4gY29tYmluZSB0aGUgZmlsZXMgKEhJTlQ6IGBiaW5kX3Jvd3MoKWAsIDIuIG1ha2UgdGhlIGxlYWRpbmcgZG90IGEgZGlmZmVyZW50IGNvbG9yIGRlcGVuZGluZyBvbiB0aGUgZXZlbnQgKGZvciBhbiBleHRyYSBjaGFsbGVuZ2UsIG1ha2UgaXQgYSBkaWZmZXJlbnQgaW1hZ2UgdXNpbmcgYGdlb21faW1hZ2UoKSEpLCAzLiBDSEFMTEVOR0UgKG9wdGlvbmFsKTogY29sb3IgYnkgc3BlZWQsIHdoaWNoIHlvdSB3aWxsIG5lZWQgdG8gY29tcHV0ZSBvbiB5b3VyIG93biBmcm9tIHRoZSBkYXRhLiBZb3UgY2FuIHJlYWQgSGVhdGhlcidzIHJhY2UgcmVwb3J0IFtoZXJlXShodHRwczovL2hlYXRoZXJsZW5kd2F5LmNvbS8yMDE2LzAyLzEwL2lyb25tYW4tNzAtMy1wYW4tYW1lcmljYW4tY2hhbXBpb25zaGlwcy1wYW5hbWEtcmFjZS1yZXBvcnQvKS4gU2hlIGlzIGFsc28gaW4gdGhlIE1hY2FsZXN0ZXIgQXRobGV0aWNzIFtIYWxsIG9mIEZhbWVdKGh0dHBzOi8vYXRobGV0aWNzLm1hY2FsZXN0ZXIuZWR1L2hvbm9ycy9oYWxsLW9mLWZhbWUvaGVhdGhlci1sZW5kd2F5LzE4NCkgYW5kIHN0aWxsIGhhcyByZWNvcmRzIGF0IHRoZSBwb29sLiAKICAKYGBge3IsIGV2YWw9RkFMU0V9CnBhbmFtYV9pcm9ubWFuIDwtIGJpbmRfcm93cyhwYW5hbWFfc3dpbSwgcGFuYW1hX2Jpa2UsIHBhbmFtYV9ydW4pCgpwYW5hbWFfbWFwIDwtIGdldF9zdGFtZW5tYXAoCiAgYmJveCA9IGMobGVmdCA9IC03OS41OSwgYm90dG9tID0gOC45MSwgcmlnaHQgPSAtNzkuNDksIHRvcCA9IDkpLAogIG1hcHR5cGUgPSAidGVycmFpbiIsCiAgem9vbSA9IDEzCikKICAKZ2dtYXAocGFuYW1hX21hcCkgKwogIGdlb21fcGF0aChkYXRhID0gcGFuYW1hX2lyb25tYW4sCiAgICAgICAgICAgIGFlcyh4ID0gbG9uLCB5ID0gbGF0LCBjb2xvciA9IGV2ZW50KSwKICAgICAgICAgICAgc2l6ZSA9IDIpICsKICB0cmFuc2l0aW9uX3JldmVhbCh0aW1lKSArCiAgbGFicyh0aXRsZSA9ICJSb3V0ZSBvZiBQYW4gQW0gSXJvbm1hbiBDaGFtcGlvbnNoaXAiLAogICAgICAgY29sb3IgPSAiRXZlbnQiKSArCiAgdGhlbWVfbWFwKCkgIC0+IGlyb25tYW5fZ2lmCgphbmltYXRlKGlyb25tYW5fZ2lmLCBuZnJhbWVzID0gMTAwLCBkdXJhdGlvbiA9IDgsIHJlbmRlcmVyID0gZ2lmc2tpX3JlbmRlcmVyKCkpIAoKYW5pbV9zYXZlKCJpcm9ubWFuLmdpZiIpCmBgYAogIAojIyBDT1ZJRC0xOSBkYXRhCgogIDYuIEluIHRoaXMgZXhlcmNpc2UsIHlvdSBhcmUgZ29pbmcgdG8gcmVwbGljYXRlIG1hbnkgb2YgdGhlIGZlYXR1cmVzIGluIFt0aGlzXShodHRwczovL2FhdGlzaGIuY29tL2NvdmlkdHJlbmRzLz9yZWdpb249VVMpIHZpc3VhbGl6YXRpb24gYnkgQWl0aXNoIEJoYXRpYSBidXQgaW5jbHVkZSBhbGwgVVMgc3RhdGVzLiBSZXF1aXJlbWVudHM6CiAqIENyZWF0ZSBhIG5ldyB2YXJpYWJsZSB0aGF0IGNvbXB1dGVzIHRoZSBudW1iZXIgb2YgbmV3IGNhc2VzIGluIHRoZSBwYXN0IHdlZWsgKEhJTlQ6IHVzZSB0aGUgYGxhZygpYCBmdW5jdGlvbiB5b3UndmUgdXNlZCBpbiBhIHByZXZpb3VzIHNldCBvZiBleGVyY2lzZXMpLiBSZXBsYWNlIG1pc3NpbmcgdmFsdWVzIHdpdGggMCdzIHVzaW5nIGByZXBsYWNlX25hKClgLiAgCiAgKiBGaWx0ZXIgdGhlIGRhdGEgdG8gb21pdCByb3dzIHdoZXJlIHRoZSBjdW11bGF0aXZlIGNhc2UgY291bnRzIGFyZSBsZXNzIHRoYW4gMjAuICAKICAqIENyZWF0ZSBhIHN0YXRpYyBwbG90IHdpdGggY3VtdWxhdGl2ZSBjYXNlcyBvbiB0aGUgeC1heGlzIGFuZCBuZXcgY2FzZXMgaW4gdGhlIHBhc3QgNyBkYXlzIG9uIHRoZSB4LWF4aXMuIENvbm5lY3QgdGhlIHBvaW50cyBmb3IgZWFjaCBzdGF0ZSBvdmVyIHRpbWUuIEhJTlRTOiB1c2UgYGdlb21fcGF0aCgpYCBhbmQgYWRkIGEgYGdyb3VwYCBhZXN0aGV0aWMuICBQdXQgdGhlIHggYW5kIHkgYXhpcyBvbiB0aGUgbG9nIHNjYWxlIGFuZCBtYWtlIHRoZSB0aWNrIGxhYmVscyBsb29rIG5pY2UgLSBgc2NhbGVzOjpjb21tYWAgaXMgb25lIG9wdGlvbi4gVGhpcyBwbG90IHdpbGwgbG9vayBwcmV0dHkgdWdseSBhcyBpcy4KICAqIEFuaW1hdGUgdGhlIHBsb3QgdG8gcmV2ZWFsIHRoZSBwYXR0ZXJuIGJ5IGRhdGUuIERpc3BsYXkgdGhlIGRhdGUgYXMgdGhlIHN1YnRpdGxlLiBBZGQgYSBsZWFkaW5nIHBvaW50IHRvIGVhY2ggc3RhdGUncyBsaW5lIChgZ2VvbV9wb2ludCgpYCkgYW5kIGFkZCB0aGUgc3RhdGUgbmFtZSBhcyBhIGxhYmVsIChgZ2VvbV90ZXh0KClgIC0geW91IHNob3VsZCBsb29rIGF0IHRoZSBgY2hlY2tfb3ZlcmxhcGAgYXJndW1lbnQpLiAgCiAgKiBVc2UgdGhlIGBhbmltYXRlKClgIGZ1bmN0aW9uIHRvIGhhdmUgMjAwIGZyYW1lcyBpbiB5b3VyIGFuaW1hdGlvbiBhbmQgbWFrZSBpdCAzMCBzZWNvbmRzIGxvbmcuIAogICogQ29tbWVudCBvbiB3aGF0IHlvdSBvYnNlcnZlLgogIAogIEluIHRoaXMgZ3JhcGggd2UgY2FuIG9ic2VydmUgdGhhdCBhbHRob3VnaCBOZXcgWW9yayB1c2VkIHRvIGhhdmUgYnkgZmFyIHRoZSBtb3N0IG5ldyB3ZWVrbHkgY2FzZXMsIEZsb3JpZGEgYW5kIENhbGlmb3JuaWEgbm93IGhhdmUgbW9yZSwgd2l0aCBGbG9yaWRhIGZhciBpbiB0aGUgbGVhZC4KYGBge3IsIGV2YWw9RkFMU0V9CmNvdmlkMTkgJT4lIAogIGdyb3VwX2J5KHN0YXRlKSAlPiUgCiAgbXV0YXRlKHdlZWtseSA9IGxhZyhjYXNlcywgbiA9IDcsIGRlZmF1bHQgPSAwKSkgJT4lIAogIGZpbHRlcih3ZWVrbHkgPj0gMjApICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBjYXNlcywgeSA9IHdlZWtseSwgY29sb3IgPSBzdGF0ZSkpICsgCiAgc2NhbGVfeF9sb2cxMChsYWJlbCA9IHNjYWxlczo6Y29tbWEpICsgCiAgc2NhbGVfeV9sb2cxMChsYWJlbCA9IHNjYWxlczo6Y29tbWEpICsgCiAgZ2VvbV9wYXRoKCkgKyAKICBnZW9tX3BvaW50KHNpemUgPSAyKSArIAogIGdlb21fdGV4dChhZXMobGFiZWwgPSBzdGF0ZSksIAogICAgICAgICAgICBjaGVja19vdmVybGFwID0gVFJVRSkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsgCiAgdHJhbnNpdGlvbl9yZXZlYWwoZGF0ZSkgKyAKICBsYWJzKHRpdGxlID0gIkNPVklEMTkgQ2FzZXMgYnkgU3RhdGUiLCBzdWJ0aXRsZSA9ICJEYXRlOiB7ZnJhbWVfYWxvbmd9IikgLT4gY292aWRfcGF0aCAKCmFuaW1hdGUoY292aWRfcGF0aCwgbmZyYW1lcyA9IDIwMCwgZHVyYXRpb24gPSAzMCwgcmVuZGVyZXIgPSBnaWZza2lfcmVuZGVyZXIoKSkgCgphbmltX3NhdmUoImNvdmlkX3BhdGguZ2lmIikKYGBgCiAgCiAgCiAgNy4gSW4gdGhpcyBleGVyY2lzZSB5b3Ugd2lsbCBhbmltYXRlIGEgbWFwIG9mIHRoZSBVUywgc2hvd2luZyBob3cgY3VtdWxhdGl2ZSBDT1ZJRC0xOSBjYXNlcyBwZXIgMTAsMDAwIHJlc2lkZW50cyBoYXMgY2hhbmdlZCBvdmVyIHRpbWUuIFRoaXMgaXMgc2ltaWxhciB0byBleGVyY2lzZXMgMTEgJiAxMiBmcm9tIHRoZSBwcmV2aW91cyBleGVyY2lzZXMsIHdpdGggdGhlIGFkZGVkIGFuaW1hdGlvbiEgU28sIGluIHRoZSBlbmQsIHlvdSBzaG91bGQgaGF2ZSBzb21ldGhpbmcgbGlrZSB0aGUgc3RhdGljIG1hcCB5b3UgbWFkZSB0aGVyZSwgYnV0IGFuaW1hdGVkIG92ZXIgYWxsIHRoZSBkYXlzLiBQdXQgZGF0ZSBpbiB0aGUgc3VidGl0bGUuIENvbW1lbnQgb24gd2hhdCB5b3Ugc2VlLgogIAogIEluIHRoaXMgbWFwIHlvdSBjYW4gc2VlIHRoYXQgYWx0aG91Z2ggV2FzaGluZ3RvbiB3YXMgdGhlIGZpcnN0IHN0YXRlIHdpdGggYSBjb25maXJtZWQgY2FzZSwgZmlyc3QgTmV3IFlvcmsgYW5kIHRoZW4gTG91aXNpYW5hIGJlY2FtZSB0aGUgY2VudGVycyBvZiB0aGUgcGFuZGVtaWMsIHdpdGggdGhlIGNlbnRlciBvZiB0aGUgY291bnRyeSByYXBpZGx5IGluY3JlYXNpbmcgdGhlcmVhZnRlci4KYGBge3IsIGV2YWw9RkFMU0V9CmNlbnN1c19wb3BfZXN0XzIwMTggPC0gcmVhZF9jc3YoImh0dHBzOi8vd3d3LmRyb3Bib3guY29tL3MvNnR4d3YzYjRuZzdwZXBlL3VzX2NlbnN1c18yMDE4X3N0YXRlX3BvcF9lc3QuY3N2P2RsPTEiKSAlPiUgCiAgc2VwYXJhdGUoc3RhdGUsIGludG8gPSBjKCJkb3QiLCJzdGF0ZSIpLCBleHRyYSA9ICJtZXJnZSIpICU+JSAKICBzZWxlY3QoLWRvdCkgJT4lIAogIG11dGF0ZShzdGF0ZSA9IHN0cl90b19sb3dlcihzdGF0ZSkpCgp1c19tYXAgPC0gbWFwX2RhdGEoInN0YXRlIikKCmNvdmlkMTkgJT4lCiAgZ3JvdXBfYnkoc3RhdGUpICU+JQogIGNvbXBsZXRlKHN0YXRlLCBkYXRlID0gc2VxLkRhdGUobWluKGRhdGUpLCBtYXgoZGF0ZSksIGJ5ID0gImRheSIpKSAlPiUgCiAgIyBtdXRhdGUoY2FzZXMgPSByZXBsYWNlX25hKGNhc2VzLCAwKSkgJT4lCiAgZmlsdGVyKHdkYXkoZGF0ZSwgbGFiZWwgPSBUUlVFKSA9PSAiRnJpIikgJT4lCiAgbXV0YXRlKHN0YXRlID0gc3RyX3RvX2xvd2VyKHN0YXRlKSkgJT4lCiAgcmlnaHRfam9pbihjZW5zdXNfcG9wX2VzdF8yMDE4LAogICAgICAgICAgICBieSA9ICJzdGF0ZSIpICU+JQogIG11dGF0ZShjYXNlc19wZXJfMTAwMDAgPSBjYXNlcy9lc3RfcG9wXzIwMTgqMTAwMDApICU+JQogIGdncGxvdCgpICsKICBnZW9tX21hcChhZXMobWFwX2lkID0gc3RhdGUsCiAgICAgICAgICAgICAgIGZpbGwgPSBjYXNlc19wZXJfMTAwMDAsCiAgICAgICAgICAgICAgIGdyb3VwID0gZGF0ZSksCiAgICAgICAgICAgbWFwID0gdXNfbWFwKSArCiAgdGhlbWVfbWFwKCkgKwogIGxhYnModGl0bGUgPSAiQ292aWQgQ2FzZXMgcGVyIDEwLDAwMCIsCiAgICAgICBzdWJ0aXRsZSA9ICJEYXRlOiB7Y2xvc2VzdF9zdGF0ZX0iLAogICAgICAgZmlsbCA9ICJDYXNlcyIpICsKICBleHBhbmRfbGltaXRzKHggPSB1c19tYXAkbG9uZywgeSA9IHVzX21hcCRsYXQpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb24gPSAidmlyaWRpcyIpICsKICB0aGVtZShsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRyYW5zaXRpb25fc3RhdGVzKGRhdGUpIC0+IGNvdmlkX21hcAogIAphbmltYXRlKGNvdmlkX21hcCwgbmZyYW1lcyA9IDEwMCwgZHVyYXRpb24gPSAzMCwgcmVuZGVyZXIgPSBnaWZza2lfcmVuZGVyZXIoKSkgCgphbmltX3NhdmUoImNvdmlkX21hcC5naWYiKQpgYGAKICAKCiMjIFlvdXIgZmlyc3QgYHNoaW55YCBhcHAKCiAgOC4gVGhpcyBhcHAgd2lsbCBhbHNvIHVzZSB0aGUgQ09WSUQgZGF0YS4gTWFrZSBzdXJlIHlvdSBsb2FkIHRoYXQgZGF0YSBhbmQgYWxsIHRoZSBsaWJyYXJpZXMgeW91IG5lZWQgaW4gdGhlIGBhcHAuUmAgZmlsZSB5b3UgY3JlYXRlLiBCZWxvdywgeW91IHdpbGwgcG9zdCBhIGxpbmsgdG8gdGhlIGFwcCB0aGF0IHlvdSBwdWJsaXNoIG9uIHNoaW55YXBwcy5pby4gWW91IHdpbGwgY3JlYXRlIGFuIGFwcCB0byBjb21wYXJlIHN0YXRlcycgY3VtdWxhdGl2ZSBudW1iZXIgb2YgQ09WSUQgY2FzZXMgb3ZlciB0aW1lLiBUaGUgeC1heGlzIHdpbGwgYmUgbnVtYmVyIG9mIGRheXMgc2luY2UgMjArIGNhc2VzIGFuZCB0aGUgeS1heGlzIHdpbGwgYmUgY3VtdWxhdGl2ZSBjYXNlcyBvbiB0aGUgbG9nIHNjYWxlIChgc2NhbGVfeV9sb2cxMCgpYCkuIFdlIHVzZSBudW1iZXIgb2YgZGF5cyBzaW5jZSAyMCsgY2FzZXMgb24gdGhlIHgtYXhpcyBzbyB3ZSBjYW4gbWFrZSBiZXR0ZXIgY29tcGFyaXNvbnMgb2YgdGhlIGN1cnZlIHRyYWplY3Rvcmllcy4gWW91IHdpbGwgaGF2ZSBhbiBpbnB1dCBib3ggd2hlcmUgdGhlIHVzZXIgY2FuIGNob29zZSB3aGljaCBzdGF0ZXMgdG8gY29tcGFyZSAoYHNlbGVjdElucHV0KClgKSBhbmQgaGF2ZSBhIHN1Ym1pdCBidXR0b24gdG8gY2xpY2sgb25jZSB0aGUgdXNlciBoYXMgY2hvc2VuIGFsbCBzdGF0ZXMgdGhleSdyZSBpbnRlcmVzdGVkIGluIGNvbXBhcmluZy4gVGhlIGdyYXBoIHNob3VsZCBkaXNwbGF5IGEgZGlmZmVyZW50IGxpbmUgZm9yIGVhY2ggc3RhdGUsIHdpdGggbGFiZWxzIGVpdGhlciBvbiB0aGUgZ3JhcGggb3IgaW4gYSBsZWdlbmQuIENvbG9yIGNhbiBiZSB1c2VkIGlmIG5lZWRlZC4gCiAgCiMjIEdpdEh1YiBsaW5rCgogIDkuIEJlbG93LCBwcm92aWRlIGEgbGluayB0byB5b3VyIEdpdEh1YiBwYWdlIHdpdGggdGhpcyBzZXQgb2YgV2Vla2x5IEV4ZXJjaXNlcy4gU3BlY2lmaWNhbGx5LCBpZiB0aGUgbmFtZSBvZiB0aGUgZmlsZSBpcyAwNV9leGVyY2lzZXMuUm1kLCBwcm92aWRlIGEgbGluayB0byB0aGUgMDVfZXhlcmNpc2VzLm1kIGZpbGUsIHdoaWNoIGlzIHRoZSBvbmUgdGhhdCB3aWxsIGJlIG1vc3QgcmVhZGFibGUgb24gR2l0SHViLiBJZiB0aGF0IGZpbGUgaXNuJ3QgdmVyeSByZWFkYWJsZSwgdGhlbiBwcm92aWRlIGEgbGluayB0byB5b3VyIG1haW4gR2l0SHViIHBhZ2UuCgpodHRwczovL2dpdGh1Yi5jb20vY2tvbGxtZXIwMS93ZWVrbHlfZXhlcmNpc2VzXzUKCioqRElEIFlPVSBSRU1FTUJFUiBUTyBVTkNPTU1FTlQgVEhFIE9QVElPTlMgQVQgVEhFIFRPUD8qKgo=